# frozen_string_literal: true

module Wpxf
  # The base class for all modules.
  class Module
    include Wpxf::ModuleInfo
    include Wpxf::OutputEmitters
    include Wpxf::Options
    include Wpxf::Net::HttpClient
    include Wpxf::WordPress::Fingerprint
    include Wpxf::WordPress::Login
    include Wpxf::WordPress::Options
    include Wpxf::WordPress::Urls
    include Wpxf::ModuleAuthentication
    include Wpxf::Db::Credentials

    def initialize
      super

      register_option(
        BooleanOption.new(
          name: 'verbose',
          desc: 'Enable verbose output',
          required: true,
          default: false
        )
      )

      register_advanced_options([
        BooleanOption.new(
          name: 'check_wordpress_and_online',
          desc: 'Check that the target is running WordPress and is online',
          required: true,
          default: true
        )
      ])

      self.event_emitter = EventEmitter.new
    end

    # @return [Boolean] true if all the required options are set.
    def can_execute?
      all_options_valid? && (aux_module? || (payload&.all_options_valid?))
    end

    # @return [Boolean] true if the target is running WordPress.
    def check_wordpress_and_online
      unless wordpress_and_online?
        emit_error "#{full_uri} does not appear to be running WordPress"
        return false
      end

      true
    end

    # @return [Array] an array of missing option names that are required.
    def missing_options
      opts = super
      opts.push('payload') if exploit_module? && !payload

      if payload
        payload_opts = payload.missing_options
        opts = [*opts, *payload_opts] unless payload_opts.empty?
      end

      opts
    end

    # Set the value of a module option.
    # @param name the name of the option to set.
    # @param value the value to use.
    # @return [String, Symbol] the normalized value, :invalid if the
    #   specified value is invalid or :not_found if the name is invalid.
    def set_option_value(name, value)
      res = super(name, value)

      if payload
        return payload.set_option_value(name, value) if res == :not_found
        payload.set_option_value(name, value)
      end

      res
    end

    # Unset an option or reset it back to its default value.
    # @param name [String] the name of the option to unset.
    def unset_option(name)
      super(name)
      payload&.unset_option(name)
    end

    # Run the module.
    # @return [Boolean] true if successful.
    def run
      if normalized_option_value('check_wordpress_and_online')
        return false unless check_wordpress_and_online
      end

      if requires_authentication
        @session_cookie = authenticate_with_wordpress(datastore['username'], datastore['password'])
        return false unless @session_cookie
      end

      true
    end

    # Cleanup any allocated resource to the module.
    def cleanup
      payload&.cleanup
    end

    # Check if the target is vulnerable.
    # @return [Symbol] :unknown, :vulnerable or :safe.
    def check
      :unknown
    end

    # @return [Boolean] true if the module is an auxiliary module.
    def aux_module?
      to_s.split('::')[-2].eql? 'Auxiliary'
    end

    # @return [Boolean] true if the module is an exploit module.
    def exploit_module?
      to_s.split('::')[-2].eql? 'Exploit'
    end

    # @return [Payload] the {Payload} to use with the current module.
    attr_accessor :payload

    # @return [EventEmitter] the {EventEmitter} for the module's events.
    attr_accessor :event_emitter

    # @return [Models::Workspace] the currently active {Models::Workspace}.
    attr_accessor :active_workspace

    # @return [String, nil] the current session cookie, if authenticated with the target.
    attr_reader :session_cookie
  end
end
